package com.github.baichuan.mapping;

import lombok.Getter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.lang.reflect.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Getter
public class FieldMapper {
    private Class<?> clazz;

    private Field field;

    private Mapping mapping;

    public FieldMapper(Class<?> clazz, Field field, Mapping mapping) {
        this.clazz = clazz;
        this.field = field;
        this.mapping = mapping;
    }

    public Object map(Object obj)
            throws NoSuchFieldException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Object value = getValue(obj);

        try{
            /**
             * 目标字段的类型是String时进行类型转换
             */
            if(field.getType().getName().equals("java.lang.String")){
                value = valueToString(value);
                return value;
            }

            if(value == null)
                return null;

            if(value.getClass() == field.getType()){
                return value;
            }

            /**
             * @Mapping 设置 convertor
             */
            if (value != null && mapping.convertor() != MappingConvertor.class) {
                MappingConvertor convertor = mapping.convertor().getConstructor().newInstance();
                return convertor.convert(value);
            }

            /**
             * 当目标字段是泛型时
             */
            // 检查字段是否为泛型类型
            if (value != null && field.getGenericType() instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();

                // 获取实际类型参数
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

                if(value != null && Iterable.class.isAssignableFrom(field.getType())){
                    LinkedList list = new LinkedList<>();

                    for(Object item : (Iterable)value) {
                        list.add(BeanMapper.map(item, (Class<? extends Object>) actualTypeArguments[0]));
                    }

                    value = list;
                }

                return value;
            }

            // 目标字段是class时
            if(value != null && !field.getType().isPrimitive() && !Iterable.class.isAssignableFrom(field.getType())){
                value = BeanMapper.map(value, field.getType());
                return value;
            }

            return value;
        }
        catch (Exception ex){
            throw new MappingException(clazz.getName() + "映射字段" + field.getName() + "时发生错误:" + ex.getMessage(), ex);
        }
    }

    /**
     * 反射获取对象字段值
     *
     * @param obj     映射数据源
     * @return
     */
    public Object getValue(Object obj)
            throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String fieldBranch = field.getName();
        if (!"".equals(mapping.value())) {
            fieldBranch = mapping.value();
        }

        Member[] fields = getMemberBranch(obj.getClass(), fieldBranch);
        Object value = obj;
        for (Member member : fields) {
            if (member instanceof Method) {
                Method method = (Method) member;
                method.setAccessible(true);
                value = method.invoke(value);
            } else if (member instanceof Field) {
                Field field = (Field) member;
                field.setAccessible(true);
                value = field.get(value);
            }
            else if (member == null && value instanceof byte[]) {
                return Hex.encodeHexString((byte[]) value);
            } else {
                value = null;
            }

            if(value == null)
                return null;
        }

        return value;
    }

    /**
     * 获取字段分支
     *
     * @param clazz
     * @param branch
     * @return
     * @throws NoSuchFieldException
     */
    public Member[] getMemberBranch(Class<?> clazz, String branch) throws NoSuchFieldException, NoSuchMethodException {
        String[] names = branch.split("\\.");
        Member[] fields = new Member[names.length];
        for (int i = 0; i < fields.length; i++) {
            String name = names[i];
            if(name.endsWith("()")){
                //无参方法，如size(),length()等
                name = name.replaceAll("\\(\\)", "");
                Method method = clazz.getMethod(name);
                clazz = method.getReturnType();
                fields[i] = method;
                continue;
            }

            //get方法
            String methodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
            if(hasMethod(clazz, methodName)){
                Method method = clazz.getDeclaredMethod(methodName);
                clazz = method.getReturnType();
                fields[i] = method;
                continue;
            }

            Field field = clazz.getDeclaredField(name);
            if(field != null){
                //字段
                clazz = field.getType();
                fields[i] = field;
                continue;
            }

            throw new RuntimeException(clazz.getName() + "不存在类成员" + name);
        }
        return fields;
    }

    private String valueToString(Object value) {
        if (value == null && mapping.nullDisplayText() != null) {
            return mapping.nullDisplayText();
        }

        if (value instanceof Enum) {
            return EnumMapper.description((Enum) value);
        }

        if (StringUtils.isNotBlank(mapping.format())) {
            //使用format格式
            if (value instanceof Date) {
                return DateFormatUtils.format((Date) value, mapping.format());
            } else if (value instanceof LocalDateTime || value instanceof LocalDate || value instanceof LocalTime) {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(mapping.format());
                return formatter.format((TemporalAccessor) value);
            } else {
                return String.format(mapping.format(), value);
            }
        }

        return value.toString();
    }

    public static boolean hasMethod(Class<?> clazz, String methodName){
        if(methodCache.containsKey(clazz)){
            return methodCache.get(clazz).contains(methodName);
        } else {
            Set<String> names = new HashSet<>();
            for (Method method : clazz.getDeclaredMethods()) {
                names.add(method.getName());
            }
            methodCache.put(clazz, names);
            return names.contains(methodName);
        }
    }

    private static Map<Class<?>, Set<String>> methodCache = new ConcurrentHashMap<>();
}
